/*
Filename:    address.cpp
Author:      Erik Öjebo
Date:        2008-11-19
Description: Represents the address of a host (client or server). Can be
             a specific address to connect to or a wild card address to accept
             connections from.
             Wraps the structs sockaddr_in and hostent, as well as a few
             associated functions.
*/

// See header file for documentation.

#include "address.h"
#include <iostream>
#include <stdexcept>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <net/if.h>

Address::Address(int portNumber)
{
    // Clear the struct first
    memset(&socketAddress, '\0', sizeof(socketAddress));

    // Set address family
    socketAddress.sin_family = AF_INET;

    // Set port number to use
    socketAddress.sin_port = htons(portNumber);

    // Accept connections from any ip
    socketAddress.sin_addr.s_addr = INADDR_ANY;
}

Address::Address(const char* const ipAddress, int portNumber)
{
    int result;
    struct addrinfo hints, *addrs;

    // Clear the struct first
    memset(&socketAddress, '\0', sizeof(socketAddress));

    // Set hints for getaddrinfo:
    memset(&hints, '\0', sizeof(hints));
    hints.ai_family = AF_INET;

    // Get addrinfo struct from host name/address:
    result = getaddrinfo(ipAddress, NULL, &hints, &addrs);

    // If an error occurred while getting the addrinfo, throw an exception:
    if (result < 0)
        throw std::runtime_error("Address constructor: Could not get addrinfo");

    // The hints to getaddrinfo specified AF_INET, so it should be a
    // sockaddr_in:
    memcpy(&socketAddress, addrs->ai_addr, sizeof(struct sockaddr_in));

    freeaddrinfo(addrs);

    // Set port number to use
    socketAddress.sin_port = htons(portNumber);

    initializeBuffers();
}

Address::Address(struct sockaddr_in const * const address)
{
    socketAddress = *address;

    initializeBuffers();
}

const char* const Address::getDottedDecimalRepresentation() const
{
    return addressBuffer;
}

const char* const Address::getHostName() const
{
    return hostnameBuffer;
}

struct sockaddr const * const Address::getSocketAddress() const
{
    return (sockaddr * const)&socketAddress;
}

size_t Address::getSocketAddressSize() const
{
    return sizeof(socketAddress);
}

void Address::initializeBuffers()
{
    int result;

    // Initialize IP address buffer:
    inet_ntop(AF_INET, &socketAddress.sin_addr.s_addr,
                addressBuffer, sizeof(addressBuffer));

    // Initialize host name buffer:
    result = getnameinfo(
            (struct sockaddr*) &socketAddress, sizeof(struct sockaddr_in),
            hostnameBuffer, sizeof(hostnameBuffer), NULL, 0, 0);

    // If an error occurred, it is not really a problem. Just make sure
    // the stored host name is a null string.
    if (result < 0)
        hostnameBuffer[0] = '\0';
}

int Address::getPortNumber() const
{
    return ntohs(socketAddress.sin_port);
}

void Address::getInterfaceInformation(struct ifconf *ifconf)
{
    int tempSocket;

    // Create a temporary socket for the ioctl call:
    if ( (tempSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP)) < 0)
        throw std::runtime_error("getInterfaceInformation: could not create socket");

    // Get the interface information
    if(ioctl(tempSocket, SIOCGIFCONF , (char*)ifconf) < 0 )
        throw std::runtime_error("getInterfaceInformation: error in ioctl");

    close(tempSocket);
}

void Address::getAllAvailable(std::vector<Address>& addresses, int portNumber)
{
    struct ifconf ifconf;           // Structure to hold all ifreq structures
    static struct ifreq ifreqs[20]; // Allocate space for a few more interfaces
                                    // than probably will be necessary.
    struct sockaddr_in *socketAddress;
    int interfaceCount;

    // Clear the ifconf structure:
    memset(&ifconf, '\0', sizeof(ifconf));

    // Set the ifconf buffer pointer to point to the allocated ifreq buffer:
    ifconf.ifc_buf = (char*) (ifreqs);

    // Set the size of the ifreqs buffer:
    ifconf.ifc_len = sizeof(ifreqs);

    // Get information for all available network interfaces:
    getInterfaceInformation(&ifconf);

    // Get the number of ifreq structs in the ifconf struct by dividing the
    // total size of the ifconf with the size of one ifreq:
    interfaceCount =  ifconf.ifc_len / sizeof(struct ifreq);

    // Create an Address instance for all interface addresses:
    for(int i = 0; i < interfaceCount; i++)
    {
        // Get a pointer to the socket address structure for the interface:
        socketAddress = (struct sockaddr_in*)&ifreqs[i].ifr_addr;

        // Set the port to the value specified by the caller:
        socketAddress->sin_port = htons(portNumber);

        // Create an Address instance from the socket address structure:
        addresses.push_back(Address(socketAddress));
    }
}
